iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0
Mobile Development

Android 性能戰爭:從 Profiler 開始的 30 天優化實錄系列 第 23

# Day 23:【資源戰爭】APK 瘦身術(上):R8/Proguard 與資源壓縮

  • 分享至 

  • xImage
  •  

各位戰士,歡迎來到第二十三天的戰場。在昨天的任務中,我們學會了如何最適化地管理圖片這項最重的「軍糧」。今天,我們的目標將從單個資源轉向整個「行軍包裹」——也就是我們的 APK 檔案。

一個臃腫的 APK,就像士兵身上沉重的行囊,會直接影響作戰效率。Google Play 的數據明確顯示,APK 的大小每增加 6MB,應用程式的安裝轉換率就會下降 1%。在網路環境不佳或儲存空間有限的地區,一個小巧的 APK 是贏得使用者的第一道關卡。

今天的任務,就是學習如何使用 Android 內建的最強大的瘦身武器——R8資源壓縮——來為我們的應用程式減負。


戰前偵查:使用 APK Analyzer 剖析敵情

在動手優化之前,我們必須先了解我們的敵人——APK 的組成結構。知己知彼,方能百戰不殆。Android Studio 內建的 APK Analyzer 就是我們的偵察機。

  1. 在 Android Studio 菜單欄選擇 Build > Build Bundle(s) / APK(s) > Build APK(s)
  2. 建構完成後,右下角會彈出提示,點擊 Analyze。或者,你也可以選擇 Build > Analyze APK... 並選中剛剛產生的 APK 檔案。

打開後,你會看到 APK 內部檔案的詳細分佈:

  • classes.dex: 這是我們所有 Kotlin/Java 程式碼被編譯後的檔案。它是 R8 的主要戰場。
  • res/: 包含我們所有的資源檔案,如圖片、佈局、字串等。它是資源壓縮的主要戰場。
  • lib/: 包含 .so 結尾的原生函式庫(C/C++ 程式碼)。
  • assets/: 你放入的原始資產檔案。
  • AndroidManifest.xml: 應用程式的清單檔。

優化的第一步,永遠是測量。APK Analyzer 讓我們清楚地看到是哪部分佔用了最大的空間,從而指導我們優化的方向。


主力武器:R8 —— 程式碼的壓縮與混淆

R8 是 Android 現代化的程式碼處理工具(ProGuard 的繼任者),當你在 Release 版本中啟用它時,它會自動執行三項關鍵任務:

  1. 程式碼壓縮 (Shrinking):這是 R8 最強大的功能。它會從你應用的進入點(如 Activities)開始,遍歷整個程式碼,找出所有「可觸達」的程式碼。任何從未被使用過的類別、方法、屬性(例如,你引入的某個巨大函式庫中 90% 你沒用到的功能)都會被視為「無用程式碼」並被徹底移除
  2. 程式碼混淆 (Obfuscation):在移除了無用程式碼後,R8 會將你保留下來的類別、方法、屬性名稱,重命名為極短的無意義名稱(如 a.b.c())。這樣做有兩個好處:一是讓你的程式碼極難被反編譯和破解;二是極大地減少了 .dex 檔案的大小,因為短名稱佔用的空間更少。
  3. 程式碼優化 (Optimization):R8 還會對程式碼進行更深層的分析和重寫,以提升執行效率,例如內聯函式、移除空的 if/else 判斷等。

如何啟用?
在你的 :app 模組的 build.gradle.kts (或 build.gradle) 檔案中,找到 release 建構類型,修改如下:

// In app/build.gradle.kts
android {
    //...
    buildTypes {
        getByName("release") {
            isMinifyEnabled = true // 這一行就啟動了 R8 的所有功能
            isShrinkResources = true // 我們稍後會講到這個

            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

注意 proguard-rules.pro
R8 的靜態分析無法處理「反射 (Reflection)」這種動態呼叫的程式碼。如果你使用了像 Gson/Moshi 這類序列化函式庫,就需要手動在 proguard-rules.pro 檔案中添加 -keep 規則,告訴 R8:「不要移除或重命名這個類別,我會在執行期透過反射用到它!」

好消息是,現在絕大多數主流函式庫都會自動將自己的 keep 規則打包進來,我們需要手動設定的情況已經越來越少了。

側翼戰場:shrinkResources —— 壓縮無用資源

R8 只處理程式碼,那專案裡那些早就沒人用的圖片、佈局、字串等資源該怎麼辦?這就是 isShrinkResources = true 發揮作用的時候。

它的工作原理:
資源壓縮依賴於程式碼壓縮。在 R8 完成它的工作,移除了所有無用程式碼之後,資源壓縮工具會開始掃描剩餘的程式碼,檢查有哪些資源(如 R.drawable.my_icon)被引用了。任何完全沒有被引用的資源檔案,都會被從最終的 APK 中安全地移除。

如何啟用?

如上所示,在 release build type 中,確保 isMinifyEnabledtrue 的前提下,再將 isShrinkResources 設為 true

注意動態資源
與 R8 類似,如果你是透過拼接字串的方式動態獲取資源(例如 resources.getIdentifier("icon_" + name, ...)),壓縮工具也無法偵測到。這時,你需要在 res/raw/keep.xml 檔案中手動聲明需要保留的資源。

今日總結

今天,我們打響了 APK 瘦身的第一場戰役,掌握了兩種最有效、也是最基礎的瘦身技術:

  1. 啟用 R8 (isMinifyEnabled = true):透過壓縮、混淆和優化,從根本上縮減 classes.dex檔案的大小,並提升程式碼安全性。

  2. 啟用資源壓縮 (isShrinkResources = true):在 R8 的基礎上,自動移除專案中所有未被引用的資源檔案。

  3. 使用 APK Analyzer:在優化前後對比 APK 的大小和組成,量化我們的戰果。

我們已經成功地為 APK 的「內容」進行了瘦身。但是,現代化的戰爭不僅僅是武器本身,更是「投放方式」的革新。明天,我們將學習更進階的瘦身術:【App Bundles 與動態交付】,它將徹底改變我們交付應用的方式,為使用者帶來極致的大小優化。

我們明天見!


上一篇
# Day 22:【資源戰爭】圖片的最適化載入 (Image Loading)
下一篇
# Day 24:【資源戰爭】APK 瘦身術(下):App Bundles 與動態交付
系列文
Android 性能戰爭:從 Profiler 開始的 30 天優化實錄24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言